/*
 * Copyright (C) 2009 Leandro Aparecido <lehphyro@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.metalframework.jsf.navigation;

import java.io.*;
import java.util.*;

import javax.faces.*;
import javax.faces.application.*;
import javax.faces.component.*;
import javax.faces.context.*;

import org.metalframework.exception.*;
import org.metalframework.jsf.action.*;
import org.metalframework.util.*;
import org.slf4j.*;

public class AnnotationBasedNavigationHandler extends NavigationHandler {

	private static Logger logger = LoggerFactory.getLogger(AnnotationBasedNavigationHandler.class);
	
	private NavigationRuleParser parser;
	
	public AnnotationBasedNavigationHandler() {
		parser = new NavigationRuleParser();
	}

	@Override
	public void handleNavigation(FacesContext context, String fromAction, String outcome) {
		logger.debug("Handling navigation from [{}]", fromAction);
		
		ActionInvocation invocation = ActionInvocationAccessor.getActionInvocation();
		
		if (invocation == null) {
			logger.debug("No navigation rules have been defined for action [{}], redisplaying current view", fromAction);
		} else if (invocation.getThrowable() != null &&
				   DomainValidationException.class.isAssignableFrom(invocation.getThrowable().getClass())) {
			logger.debug("Validation errors have been detected, redisplaying current view");
		} else {
			List<NavigationRule> rules = parser.parse(invocation.getMethod());
			NavigationRule rule = findActivatedRule(rules, invocation.getReturnValue(), invocation.getThrowable());
			
			if (rule == null) {
				logger.debug("No navigation rule has been activated from [{}], redisplaying current view", fromAction);
			} else {
				logger.debug("Navigation rule [{}] has been activated from [{}], resolving next view", rule, fromAction);
				
				if (rule.getViewId() == null) {
					logger.debug("Navigation rule [{}] requires to stay on current view", rule);
				} else {
					String resolvedView = FacesUtils.resolve(rule.getViewId(), String.class, false);
					
					if (rule.isRedirect()) {
						logger.debug("Redirecting to [{}] according to navigation rule [{}]", resolvedView, rule);
						
		                ViewHandler viewHandler = context.getApplication().getViewHandler();
		                String redirectPath = viewHandler.getActionURL(context, resolvedView);
		                ExternalContext externalContext = context.getExternalContext();
		                try {
							externalContext.redirect(externalContext.encodeActionURL(redirectPath));
						} catch (IOException e) {
							throw new FacesException(e.getMessage(), e);
						}
					} else {
						logger.debug("Forwarding to [{}] according to navigation rule [{}]", resolvedView, rule);
	
						ViewHandler viewHandler = context.getApplication().getViewHandler();
		                UIViewRoot viewRoot = viewHandler.createView(context, resolvedView);
		                context.setViewRoot(viewRoot);
		                context.renderResponse();
					}
				}
			}
		}
	}

	protected NavigationRule findActivatedRule(List<NavigationRule> rules, Object returnValue, Throwable throwable) {
		NavigationRule fallbackRule = null;
		
		for (NavigationRule rule : rules) {
			if (rule.isFallback()) {
				fallbackRule = rule;
			}
			if (rule.isActivated(returnValue, throwable)) {
				return rule;
			}
		}
		
		if (fallbackRule == null &&	rules.size() == 1 && rules.get(0).isDefaultCondition()) {
			fallbackRule = rules.get(0);
		}
		
		return fallbackRule;
	}
	
}
